Skip to main content

Strings

A comprehensive guide to string algorithms and techniques for Data Structures and Algorithms.

Table of Contents

  1. Basic String Operations
  2. Pattern Matching Algorithms
  3. String Manipulation Techniques
  4. Palindrome Techniques
  5. Anagram and Permutation
  6. Sliding Window Techniques
  7. Two Pointer Techniques
  8. Advanced String Algorithms
  9. Dynamic Programming with Strings
  10. Usage Examples

Basic String Operations

1. String Traversal

function traverseString(str) {
const result = [];

// Method 1: Traditional for loop
for (let i = 0; i < str.length; i++) {
result.push(str[i]);
}

// Method 2: for...of loop
for (const char of str) {
result.push(char);
}

return result;
}

Time Complexity: O(n) | Space Complexity: O(1)

2. Character Frequency Count

function charFrequency(str) {
const freq = {};

for (const char of str) {
freq[char] = (freq[char] || 0) + 1;
}

return freq;
}

// Using Map for better performance
function charFrequencyMap(str) {
const freq = new Map();

for (const char of str) {
freq.set(char, (freq.get(char) || 0) + 1);
}

return freq;
}

3. String Comparison

function compareStrings(str1, str2) {
if (str1.length !== str2.length) return false;

for (let i = 0; i < str1.length; i++) {
if (str1[i] !== str2[i]) return false;
}

return true;
}

// Case-insensitive comparison
function compareIgnoreCase(str1, str2) {
return str1.toLowerCase() === str2.toLowerCase();
}

4. String Conversion Utilities

// Convert to different cases
function toCamelCase(str) {
return str.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase());
}

function toSnakeCase(str) {
return str.replace(/\W+/g, " ")
.split(/ |\B(?=[A-Z])/)
.map(word => word.toLowerCase())
.join('_');
}

function toKebabCase(str) {
return str.replace(/\W+/g, " ")
.split(/ |\B(?=[A-Z])/)
.map(word => word.toLowerCase())
.join('-');
}

Pattern Matching Algorithms

function naiveSearch(text, pattern) {
const matches = [];
const n = text.length;
const m = pattern.length;

for (let i = 0; i <= n - m; i++) {
let j = 0;

while (j < m && text[i + j] === pattern[j]) {
j++;
}

if (j === m) {
matches.push(i);
}
}

return matches;
}

Time Complexity: O(nm) | Space Complexity: O(1)

2. KMP (Knuth-Morris-Pratt) Algorithm

function computeLPS(pattern) {
const lps = new Array(pattern.length).fill(0);
let len = 0;
let i = 1;

while (i < pattern.length) {
if (pattern[i] === pattern[len]) {
len++;
lps[i] = len;
i++;
} else {
if (len !== 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}

return lps;
}

function KMPSearch(text, pattern) {
const matches = [];
const n = text.length;
const m = pattern.length;
const lps = computeLPS(pattern);

let i = 0; // text index
let j = 0; // pattern index

while (i < n) {
if (pattern[j] === text[i]) {
i++;
j++;
}

if (j === m) {
matches.push(i - j);
j = lps[j - 1];
} else if (i < n && pattern[j] !== text[i]) {
if (j !== 0) {
j = lps[j - 1];
} else {
i++;
}
}
}

return matches;
}

Time Complexity: O(n + m) | Space Complexity: O(m)

3. Rabin-Karp Algorithm

function rabinKarp(text, pattern, prime = 101) {
const matches = [];
const n = text.length;
const m = pattern.length;
const base = 256;

let patternHash = 0;
let textHash = 0;
let h = 1;

// Calculate h = base^(m-1) % prime
for (let i = 0; i < m - 1; i++) {
h = (h * base) % prime;
}

// Calculate hash for pattern and first window of text
for (let i = 0; i < m; i++) {
patternHash = (base * patternHash + pattern.charCodeAt(i)) % prime;
textHash = (base * textHash + text.charCodeAt(i)) % prime;
}

// Slide pattern over text
for (let i = 0; i <= n - m; i++) {
if (patternHash === textHash) {
// Check characters one by one
let match = true;
for (let j = 0; j < m; j++) {
if (text[i + j] !== pattern[j]) {
match = false;
break;
}
}
if (match) matches.push(i);
}

// Calculate hash for next window
if (i < n - m) {
textHash = (base * (textHash - text.charCodeAt(i) * h) + text.charCodeAt(i + m)) % prime;
if (textHash < 0) textHash += prime;
}
}

return matches;
}

Time Complexity: O(nm) worst case, O(n + m) average | Space Complexity: O(1)


String Manipulation Techniques

1. String Reversal

// Method 1: Built-in methods
function reverseString1(str) {
return str.split('').reverse().join('');
}

// Method 2: Two pointers
function reverseString2(str) {
const chars = str.split('');
let left = 0;
let right = chars.length - 1;

while (left < right) {
[chars[left], chars[right]] = [chars[right], chars[left]];
left++;
right--;
}

return chars.join('');
}

// Method 3: Recursive
function reverseString3(str) {
if (str.length <= 1) return str;
return str[str.length - 1] + reverseString3(str.slice(0, -1));
}

2. Remove Characters

function removeChar(str, charToRemove) {
return str.split('').filter(char => char !== charToRemove).join('');
}

function removeCharsRegex(str, pattern) {
return str.replace(new RegExp(pattern, 'g'), '');
}

function removeVowels(str) {
return str.replace(/[aeiouAEIOU]/g, '');
}

function removeConsonants(str) {
return str.replace(/[^aeiouAEIOU\s]/g, '');
}

3. String Compression

function compress(str) {
if (!str) return '';

let compressed = '';
let count = 1;

for (let i = 1; i < str.length; i++) {
if (str[i] === str[i - 1]) {
count++;
} else {
compressed += str[i - 1] + (count > 1 ? count : '');
count = 1;
}
}

// Add the last character group
compressed += str[str.length - 1] + (count > 1 ? count : '');

return compressed.length < str.length ? compressed : str;
}

function decompress(str) {
let result = '';
let i = 0;

while (i < str.length) {
const char = str[i];
let count = '';
i++;

// Read the number
while (i < str.length && !isNaN(str[i])) {
count += str[i];
i++;
}

const repeatCount = count === '' ? 1 : parseInt(count);
result += char.repeat(repeatCount);
}

return result;
}

Palindrome Techniques

1. Check if String is Palindrome

// Simple approach
function isPalindrome1(str) {
const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '');
return cleaned === cleaned.split('').reverse().join('');
}

// Two pointers approach
function isPalindrome2(str) {
const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '');
let left = 0;
let right = cleaned.length - 1;

while (left < right) {
if (cleaned[left] !== cleaned[right]) {
return false;
}
left++;
right--;
}

return true;
}

// Recursive approach
function isPalindromeRecursive(str, start = 0, end = str.length - 1) {
if (start >= end) return true;
if (str[start] !== str[end]) return false;
return isPalindromeRecursive(str, start + 1, end - 1);
}

2. Longest Palindromic Substring

// Expand around centers
function longestPalindrome(s) {
if (!s || s.length < 2) return s || '';

let start = 0;
let maxLength = 1;

function expandAroundCenter(left, right) {
while (left >= 0 && right < s.length && s[left] === s[right]) {
const currentLength = right - left + 1;
if (currentLength > maxLength) {
start = left;
maxLength = currentLength;
}
left--;
right++;
}
}

for (let i = 0; i < s.length; i++) {
expandAroundCenter(i, i); // Odd length palindromes
expandAroundCenter(i, i + 1); // Even length palindromes
}

return s.substring(start, start + maxLength);
}

3. All Palindromic Substrings

function countPalindromes(s) {
let count = 0;

function expandAroundCenter(left, right) {
while (left >= 0 && right < s.length && s[left] === s[right]) {
count++;
left--;
right++;
}
}

for (let i = 0; i < s.length; i++) {
expandAroundCenter(i, i); // Odd length
expandAroundCenter(i, i + 1); // Even length
}

return count;
}

function getAllPalindromes(s) {
const palindromes = [];

function expandAroundCenter(left, right) {
while (left >= 0 && right < s.length && s[left] === s[right]) {
palindromes.push(s.substring(left, right + 1));
left--;
right++;
}
}

for (let i = 0; i < s.length; i++) {
expandAroundCenter(i, i);
expandAroundCenter(i, i + 1);
}

return [...new Set(palindromes)]; // Remove duplicates
}

Anagram and Permutation

1. Check if Two Strings are Anagrams

// Sorting approach
function isAnagram1(str1, str2) {
if (str1.length !== str2.length) return false;

const sorted1 = str1.toLowerCase().split('').sort().join('');
const sorted2 = str2.toLowerCase().split('').sort().join('');

return sorted1 === sorted2;
}

// Character frequency approach
function isAnagram2(str1, str2) {
if (str1.length !== str2.length) return false;

const freq = {};

// Count characters in first string
for (const char of str1.toLowerCase()) {
freq[char] = (freq[char] || 0) + 1;
}

// Subtract characters from second string
for (const char of str2.toLowerCase()) {
if (!freq[char]) return false;
freq[char]--;
}

return true;
}

2. Group Anagrams

function groupAnagrams(strs) {
const groups = {};

for (const str of strs) {
const key = str.split('').sort().join('');
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(str);
}

return Object.values(groups);
}

// Alternative approach using character frequency
function groupAnagramsFreq(strs) {
const groups = {};

for (const str of strs) {
const freq = new Array(26).fill(0);
for (const char of str) {
freq[char.charCodeAt(0) - 'a'.charCodeAt(0)]++;
}
const key = freq.join(',');

if (!groups[key]) {
groups[key] = [];
}
groups[key].push(str);
}

return Object.values(groups);
}

3. Generate All Permutations

function permutations(str) {
if (str.length <= 1) return [str];

const result = [];

for (let i = 0; i < str.length; i++) {
const char = str[i];
const remaining = str.slice(0, i) + str.slice(i + 1);
const perms = permutations(remaining);

for (const perm of perms) {
result.push(char + perm);
}
}

return result;
}

// Iterative approach
function permutationsIterative(str) {
let perms = [''];

for (const char of str) {
const newPerms = [];
for (const perm of perms) {
for (let i = 0; i <= perm.length; i++) {
newPerms.push(perm.slice(0, i) + char + perm.slice(i));
}
}
perms = newPerms;
}

return perms;
}

Sliding Window Techniques

1. Longest Substring Without Repeating Characters

function lengthOfLongestSubstring(s) {
const seen = new Set();
let left = 0;
let maxLength = 0;

for (let right = 0; right < s.length; right++) {
while (seen.has(s[right])) {
seen.delete(s[left]);
left++;
}

seen.add(s[right]);
maxLength = Math.max(maxLength, right - left + 1);
}

return maxLength;
}

2. Minimum Window Substring

function minWindow(s, t) {
if (s.length < t.length) return '';

const tFreq = {};
for (const char of t) {
tFreq[char] = (tFreq[char] || 0) + 1;
}

let left = 0;
let minLen = Infinity;
let minStart = 0;
let matched = 0;
const windowFreq = {};

for (let right = 0; right < s.length; right++) {
const rightChar = s[right];
windowFreq[rightChar] = (windowFreq[rightChar] || 0) + 1;

if (tFreq[rightChar] && windowFreq[rightChar] === tFreq[rightChar]) {
matched++;
}

while (matched === Object.keys(tFreq).length) {
if (right - left + 1 < minLen) {
minLen = right - left + 1;
minStart = left;
}

const leftChar = s[left];
windowFreq[leftChar]--;
if (tFreq[leftChar] && windowFreq[leftChar] < tFreq[leftChar]) {
matched--;
}
left++;
}
}

return minLen === Infinity ? '' : s.substring(minStart, minStart + minLen);
}

3. Longest Repeating Character Replacement

function characterReplacement(s, k) {
const freq = {};
let left = 0;
let maxFreq = 0;
let maxLength = 0;

for (let right = 0; right < s.length; right++) {
freq[s[right]] = (freq[s[right]] || 0) + 1;
maxFreq = Math.max(maxFreq, freq[s[right]]);

// If window size - max frequency > k, shrink window
if (right - left + 1 - maxFreq > k) {
freq[s[left]]--;
left++;
}

maxLength = Math.max(maxLength, right - left + 1);
}

return maxLength;
}

Two Pointer Techniques

1. Valid Palindrome

function isPalindromeAlphanumeric(s) {
let left = 0;
let right = s.length - 1;

while (left < right) {
// Skip non-alphanumeric characters
while (left < right && !isAlphanumeric(s[left])) {
left++;
}
while (left < right && !isAlphanumeric(s[right])) {
right--;
}

if (s[left].toLowerCase() !== s[right].toLowerCase()) {
return false;
}

left++;
right--;
}

return true;
}

function isAlphanumeric(char) {
const code = char.charCodeAt(0);
return (code >= 48 && code <= 57) || // 0-9
(code >= 65 && code <= 90) || // A-Z
(code >= 97 && code <= 122); // a-z
}

2. Two Sum in Sorted Array (String Version)

function twoSumStrings(strings, target) {
let left = 0;
let right = strings.length - 1;

while (left < right) {
const current = strings[left] + strings[right];

if (current === target) {
return [left, right];
} else if (current < target) {
left++;
} else {
right--;
}
}

return [-1, -1];
}

3. Container With Most Water (String Analogy)

function longestCommonPrefix(strs) {
if (!strs || strs.length === 0) return '';

let prefix = strs[0];

for (let i = 1; i < strs.length; i++) {
while (strs[i].indexOf(prefix) !== 0) {
prefix = prefix.substring(0, prefix.length - 1);
if (prefix === '') return '';
}
}

return prefix;
}

Advanced String Algorithms

1. Z Algorithm

function zAlgorithm(s) {
const n = s.length;
const z = new Array(n).fill(0);
let left = 0;
let right = 0;

for (let i = 1; i < n; i++) {
if (i <= right) {
z[i] = Math.min(right - i + 1, z[i - left]);
}

while (i + z[i] < n && s[z[i]] === s[i + z[i]]) {
z[i]++;
}

if (i + z[i] - 1 > right) {
left = i;
right = i + z[i] - 1;
}
}

return z;
}

function searchWithZ(text, pattern) {
const combined = pattern + '$' + text;
const z = zAlgorithm(combined);
const matches = [];

for (let i = 0; i < z.length; i++) {
if (z[i] === pattern.length) {
matches.push(i - pattern.length - 1);
}
}

return matches;
}

2. Manacher's Algorithm (Longest Palindromic Substring)

function manacher(s) {
// Transform string: "abc" -> "^#a#b#c#$"
const processed = '^#' + s.split('').join('#') + '#$';
const n = processed.length;
const p = new Array(n).fill(0);
let center = 0;
let right = 0;

for (let i = 1; i < n - 1; i++) {
const mirror = 2 * center - i;

if (i < right) {
p[i] = Math.min(right - i, p[mirror]);
}

// Expand around center i
while (processed[i + (1 + p[i])] === processed[i - (1 + p[i])]) {
p[i]++;
}

// If palindrome centered at i extends past right, adjust center and right
if (i + p[i] > right) {
center = i;
right = i + p[i];
}
}

return p;
}

function longestPalindromeManacher(s) {
const p = manacher(s);
let maxLen = 0;
let centerIndex = 0;

for (let i = 0; i < p.length; i++) {
if (p[i] > maxLen) {
maxLen = p[i];
centerIndex = i;
}
}

const start = Math.floor((centerIndex - maxLen) / 2);
return s.substring(start, start + maxLen);
}

3. Suffix Array

function buildSuffixArray(s) {
const n = s.length;
const suffixes = [];

for (let i = 0; i < n; i++) {
suffixes.push({
index: i,
suffix: s.substring(i)
});
}

suffixes.sort((a, b) => a.suffix.localeCompare(b.suffix));

return suffixes.map(suffix => suffix.index);
}

function longestCommonSubstring(str1, str2) {
const combined = str1 + '#' + str2;
const suffixArray = buildSuffixArray(combined);
const n = combined.length;

let maxLength = 0;
let result = '';

for (let i = 0; i < n - 1; i++) {
const idx1 = suffixArray[i];
const idx2 = suffixArray[i + 1];

// Check if suffixes are from different strings
const fromDifferentStrings =
(idx1 < str1.length && idx2 > str1.length) ||
(idx1 > str1.length && idx2 < str1.length);

if (fromDifferentStrings) {
const suffix1 = combined.substring(idx1);
const suffix2 = combined.substring(idx2);

let commonLength = 0;
const minLength = Math.min(suffix1.length, suffix2.length);

while (commonLength < minLength &&
suffix1[commonLength] === suffix2[commonLength]) {
commonLength++;
}

if (commonLength > maxLength) {
maxLength = commonLength;
result = suffix1.substring(0, commonLength);
}
}
}

return result;
}

Dynamic Programming with Strings

1. Longest Common Subsequence

function longestCommonSubsequence(text1, text2) {
const m = text1.length;
const n = text2.length;
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));

for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (text1[i - 1] === text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}

return dp[m][n];
}

function getLCSString(text1, text2) {
const m = text1.length;
const n = text2.length;
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));

// Fill DP table
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (text1[i - 1] === text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}

// Reconstruct LCS
let lcs = '';
let i = m, j = n;

while (i > 0 && j > 0) {
if (text1[i - 1] === text2[j - 1]) {
lcs = text1[i - 1] + lcs;
i--;
j--;
} else if (dp[i - 1][j] > dp[i][j - 1]) {
i--;
} else {
j--;
}
}

return lcs;
}

2. Edit Distance (Levenshtein Distance)

function editDistance(word1, word2) {
const m = word1.length;
const n = word2.length;
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));

// Initialize base cases
for (let i = 0; i <= m; i++) dp[i][0] = i;
for (let j = 0; j <= n; j++) dp[0][j] = j;

for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (word1[i - 1] === word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = 1 + Math.min(
dp[i - 1][j], // deletion
dp[i][j - 1], // insertion
dp[i - 1][j - 1] // substitution
);
}
}
}

return dp[m][n];
}

3. Distinct Subsequences

function numDistinct(s, t) {
const m = s.length;
const n = t.length;
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));

// Empty subsequence can be formed in one way
for (let i = 0; i <= m; i++) {
dp[i][0] = 1;
}

for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (s[i - 1] === t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}

return dp[m][n];
}

Usage Examples

console.log("=== String Algorithms Demo ===");

// Basic operations
const str = "Hello World";
console.log("Original:", str);
console.log("Reversed:", reverseString2(str));
console.log("Character frequency:", charFrequency(str.toLowerCase()));

// Pattern matching
const text = "ABABDABACDABABCABCABCABCABC";
const pattern = "ABABCABCAB";
console.log("Naive search:", naiveSearch(text, pattern));
console.log("KMP search:", KMPSearch(text, pattern));
console.log("Rabin-Karp search:", rabinKarp(text, pattern));

// String manipulation
const testStr = "aabcccccaaa";
console.log("Compressed:", compress(testStr));
console.log("Decompressed:", decompress(compress(testStr)));

// Palindrome checks
const palindromeTests = ["racecar", "A man a plan a canal Panama", "race a car"];
palindromeTests.forEach(test => {
console.log(`"${test}" is palindrome:`, isPalindrome2(test));
});

console.log("Longest palindrome in 'babad':", longestPalindrome("babad"));
console.log("All palindromes in 'aab':", getAllPalindromes("aab"));

// Anagrams
console.log("'listen' and 'silent' are anagrams:", isAnagram2("listen", "silent"));
console.log("Group anagrams:", groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"]));

// Sliding window
console.log("Longest substring without repeating:", lengthOfLongestSubstring("abcabcbb"));
console.log("Min window substring:", minWindow("ADOBECODEBANC", "ABC"));

// Advanced algorithms
console.log("Z-algorithm for 'aabaaba':", zAlgorithm("aabaaba"));
console.log("LCS of 'ABCDGH' and 'AEDFHR':", getLCSString("ABCDGH", "AEDFHR"));
console.log("Edit distance between 'horse' and 'ros':", editDistance("horse", "ros"));

Time Complexity Summary

AlgorithmTime ComplexitySpace ComplexityUse Case
Basic Operations
TraversalO(n)O(1)Basic string processing
Character FrequencyO(n)O(k) where k = unique charsAnagram detection
String ComparisonO(n)O(1)Equality checking
Pattern Matching
Naive SearchO(nm)O(1)Simple pattern finding
KMP AlgorithmO(n + m)O(m)Efficient pattern matching
Rabin-KarpO(n + m) avg, O(nm) worstO(1)Multiple pattern search
Z AlgorithmO(n)O(n)Pattern matching, periodicity
String Manipulation
Reverse StringO(n)O(1) to O(n)String reversal
CompressionO(n)O(1)Data compression
Remove CharactersO(n)O(1) to O(n)String cleaning
Palindrome Algorithms
Palindrome CheckO(n)O(1)Validation
Longest PalindromeO(n²)O(1)Substring finding
Manacher's AlgorithmO(n)O(n)Optimal palindrome finding
Anagram & Permutation
Anagram CheckO(n log n) or O(n)O(1) or O(k)Anagram detection
Group AnagramsO(n * m log m)O(nm)Grouping similar strings
Generate PermutationsO(n! * n)O(n! * n)All arrangements
Sliding Window
Longest SubstringO(n)O(min(m,n))Substring problems
Min Window SubstringO(n + m)O(m + n)Pattern matching
Character ReplacementO(n)O(1)String modification
Dynamic Programming
LCSO(nm)O(nm)Subsequence finding
Edit DistanceO(nm)O(nm)String similarity
Distinct SubsequencesO(nm)O(nm)Counting subsequences

Common Patterns to Remember

1. Two Pointer Pattern

Perfect for palindromes and sorted array problems:

let left = 0, right = str.length - 1;
while (left < right) {
// Process characters at left and right
left++;
right--;
}

2. Sliding Window Pattern

For substring problems with constraints:

let left = 0;
for (let right = 0; right < str.length; right++) {
// Expand window
while (/* window invalid */) {
// Shrink window
left++;
}
// Update result
}

3. Character Frequency Pattern

Using Map or object for character counting:

const freq = new Map();
for (const char of str) {
freq.set(char, (freq.get(char) || 0) + 1);
}

4. Dynamic Programming Pattern

For comparing two strings:

const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (str1[i-1] === str2[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
}
}
}

5. Preprocessing Pattern

Building auxiliary data structures:

// LPS array for KMP
function computeLPS(pattern) {
const lps = new Array(pattern.length).fill(0);
let len = 0, i = 1;

while (i < pattern.length) {
if (pattern[i] === pattern[len]) {
len++;
lps[i] = len;
i++;
} else {
if (len !== 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}
return lps;
}

Key Interview Tips

1. String Immutability

Remember that strings are immutable in JavaScript:

// Inefficient - creates new string each time
let result = "";
for (let i = 0; i < n; i++) {
result += char; // O(n²) time complexity
}

// Efficient - use array then join
const chars = [];
for (let i = 0; i < n; i++) {
chars.push(char);
}
const result = chars.join(''); // O(n) time complexity

2. Edge Cases to Consider

  • Empty string ""
  • Single character string "a"
  • All same characters "aaaa"
  • No repeating characters "abcd"
  • Special characters and spaces
  • Case sensitivity

3. Common String Methods

// Essential JavaScript string methods
str.charAt(i) // Get character at index
str.charCodeAt(i) // Get ASCII code
str.substring(i, j) // Extract substring
str.slice(i, j) // Extract substring (supports negative indices)
str.indexOf(substr) // Find first occurrence
str.lastIndexOf(substr) // Find last occurrence
str.split(delimiter) // Split into array
str.replace(old, new) // Replace occurrences
str.toLowerCase() // Convert to lowercase
str.toUpperCase() // Convert to uppercase
str.trim() // Remove whitespace

4. ASCII and Character Codes

// Useful for character manipulation
'a'.charCodeAt(0) - 'a'.charCodeAt(0) = 0 // a = 97
'z'.charCodeAt(0) - 'a'.charCodeAt(0) = 25 // z = 122
'A'.charCodeAt(0) - 'A'.charCodeAt(0) = 0 // A = 65
'Z'.charCodeAt(0) - 'A'.charCodeAt(0) = 25 // Z = 90
'0'.charCodeAt(0) - '0'.charCodeAt(0) = 0 // 0 = 48
'9'.charCodeAt(0) - '0'.charCodeAt(0) = 9 // 9 = 57

// Convert between cases
const lowerToUpper = char => String.fromCharCode(char.charCodeAt(0) - 32);
const upperToLower = char => String.fromCharCode(char.charCodeAt(0) + 32);

5. Regex Patterns for Common Operations

// Remove non-alphanumeric characters
str.replace(/[^a-zA-Z0-9]/g, '');

// Remove vowels
str.replace(/[aeiouAEIOU]/g, '');

// Split on multiple delimiters
str.split(/[,\s]+/);

// Match email pattern
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);

// Match phone number
/^\d{3}-\d{3}-\d{4}$/.test(phone);

Practice Problems Categories

Easy Level

  • Reverse String
  • Valid Palindrome
  • First Unique Character
  • Valid Anagram
  • Implement strStr()
  • Length of Last Word
  • Roman to Integer

Medium Level

  • Longest Palindromic Substring
  • Longest Substring Without Repeating Characters
  • Group Anagrams
  • Minimum Window Substring
  • Longest Repeating Character Replacement
  • Palindromic Substrings
  • String Compression

Hard Level

  • Edit Distance
  • Regular Expression Matching
  • Wildcard Matching
  • Shortest Palindrome
  • Distinct Subsequences
  • Minimum Number of Taps to Open to Water a Garden
  • Text Justification

Advanced Topics for Further Study

  1. Suffix Trees and Suffix Arrays
  2. Aho-Corasick Algorithm (Multiple pattern matching)
  3. Boyer-Moore Algorithm (Pattern matching)
  4. String Hashing Techniques
  5. Finite State Automata for pattern recognition
  6. Trie Data Structure for prefix matching
  7. Rolling Hash for substring operations
  8. Burrows-Wheeler Transform for compression